package net.sourceforge.jweb.jstl.helper;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.jsp.JspException;

import net.sourceforge.jweb.annotation.Attribute;
import net.sourceforge.jweb.util.FreeMarkerUtil;

public class JstlUtil {
	private JstlUtil() {

	}

	/**
	 * 转换为使用单引号的json字符串,
	 * 
	 * @param map
	 * @return
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static String jsonify(Map<String, Object> map) {
		StringBuilder builder = new StringBuilder(map.size() << 4);
		builder.append("{\n");
		for (Entry<String, Object> entry : map.entrySet()) {
			builder.append(entry.getKey()).append(":");
			if (entry.getValue() instanceof Map) {
				builder.append(jsonify((Map) entry.getValue()));
			} else if (entry.getValue() instanceof Collection) {
				builder.append(jsonify((Collection) entry.getValue()));
			} else {
				builder.append(entry.getValue());
			}

			builder.append(",");// ' is included in value object
		}
		if (map.size() > 0) {
			builder.delete(builder.length() - 1, builder.length());// last ,\n
		}
		builder.append("}\n");
		return builder.toString();
	}

	public static String jsonify(Collection<Map<String, Object>> array) {
		StringBuilder builder = new StringBuilder(array.size() << 5);
		builder.append("[");
		for (Map<String, Object> element : array) {
			builder.append(jsonify(element)).append(",");// ' is included in value object
		}
		if (array.size() > 0) {
			builder.deleteCharAt(builder.length() - 1);// last ,
		}
		builder.append("]");
		return builder.toString();
	}

	/**
	 * 根据数据的类型自动判断水平对齐方式
	 * 
	 * @param typeName
	 * @return
	 */
	public static String getAlignment(String typeName) {
		String align = "'left'";
		if ("Date".equals(typeName)) {
			align = "'center'";
		} else if ("String".equals(typeName)) {
			align = "'left'";
		} else {// 数字居右
			align = "'right'";
		}
		return align;
	}

	/**
	 * 反射Tag对象字段，保存到map便于freemarker运行模板
	 * 
	 * @throws JspException
	 */
	public static Map<String, Object> tagAttibuteMap(Object obj) throws JspException {
		Map<String, Object> properties = new LinkedHashMap<>();
		try {
			BeanInfo tagBeaninfo = Introspector.getBeanInfo(obj.getClass());
			PropertyDescriptor[] tagProperties = tagBeaninfo.getPropertyDescriptors();
			JstlUtil.sortAsNaturalOrder(tagProperties, obj.getClass());
			for (PropertyDescriptor descriptor : tagProperties) {
				Method readMethod = descriptor.getReadMethod();
				Method writeMethod = descriptor.getWriteMethod();
				if (readMethod == null || writeMethod == null) {
					continue;
				}
				// just put attributes of this class's same package
				String classOfMethod = readMethod.getDeclaringClass().getName();
				if (!classOfMethod.startsWith(obj.getClass().getPackage().getName())) {
					continue;
				}
				String name = descriptor.getName();
				Object value = JstlUtil.getValue(descriptor.getReadMethod(), obj);
				if (value != null) {
					properties.put(name, value);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new JspException(e);
		}
		return properties;
	}

	/**
	 * 反射字段，以字符串方式打印给js,主要需要检查是否需要js的字符串 ' "
	 * 
	 * @param method
	 * @param obj
	 * @return
	 */
	public static Object getValue(Method method, Object obj) {
		try {
			Object value = method.invoke(obj);
			if (value != null) {
				if (value instanceof String) {
					String quote = "'";// 默认是单引号
					Attribute attrAnnotation = method.getDeclaredAnnotation(Attribute.class);
					if (attrAnnotation != null) {
						quote = attrAnnotation.quote();
					}
					return quote + value.toString() + quote;
				} else {
					return value;
				}
			}
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static Object getNaturalValue(Method method, Object obj) {
		try {
			return method.invoke(obj);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 使用BeanInfo读取的属性是字母排序，因此这里按照字段的自然编码顺序排序，尽量保证HTML上的表格和数据库一致
	 * 
	 * @param alphabetOdered
	 * @return
	 */
	public static PropertyDescriptor[] sortAsNaturalOrder(PropertyDescriptor[] alphabetOdered, Class<?> clazz) {
		Field[] fields = clazz.getDeclaredFields();
		Map<String, Integer> naturalOrderMap = new HashMap<>();
		int i = 0;
		for (Field f : fields) {
			naturalOrderMap.put(f.getName(), i++);
		}
		Arrays.sort(alphabetOdered, new java.util.Comparator<PropertyDescriptor>() {
			@Override
			public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
				int order1 = 0;
				int order2 = 0;
				if (naturalOrderMap.containsKey(o1.getName())) {
					order1 = naturalOrderMap.get(o1.getName());
				}
				if (naturalOrderMap.containsKey(o2.getName())) {
					order2 = naturalOrderMap.get(o2.getName());
				}
				return order1 - order2;
			}
		});
		return alphabetOdered;
	}

	/**
	 * 从字段类型生成默认的editor配置
	 * 
	 * @param fieldTypeName
	 * @return
	 */
	public static Map<String, Object> defaultDataGridEditor(String fieldTypeName) {
		Map<String, Object> ret = new LinkedHashMap<>();

		fieldTypeName = fieldTypeName.toLowerCase();
		String editorType = "textbox";
		Map<String, Object> options = new LinkedHashMap<>();
		options.put("required", "false");
		switch (fieldTypeName) {
		case "byte":
		case "short":
		case "int":
		case "long":
		case "biginteger":
			editorType = "numberbox";
			break;
		case "float":
		case "double":
		case "decimal":
		case "bigdecimal":
			options.put("precision", "2");
			editorType = "numberbox";
			break;
		case "date":
		case "calendar":
			editorType = "datebox";
			break;
		case "boolean":
			// use Y/N combox
			editorType = "combobox";
			options.put("valueField", "'value'");
			options.put("textField", "'text'");
			options.put("data", "[{value:'Y',text:'是'},{value:'N',text:'否'}]");
			break;
		}
		ret.put("type", "'" + editorType + "'");
		ret.put("options", options);

		return ret;
	}

	/**
	 * 从字段类型生成默认的过滤器的配置 Create and enable filter functionality. The 'filters'
	 * parameter is an array of filter configuration. Each item contains following
	 * properties: 1) field: the custom rule on. 2) type: the filter type, possible
	 * values are:
	 * label,text,textarea,checkbox,numberbox,validatebox,datebox,combobox,combotree.
	 * 3) options: the options of the filter type. 4) op: the filter operation,
	 * possible values are:
	 * contains,equal,notequal,beginwith,endwith,less,lessorequal,greater,greaterorequal.
	 * 
	 * Code examples
	 * 
	 * $('#dg').datagrid('enableFilter'); $('#dg').datagrid('enableFilter', [{
	 * field:'listprice', type:'numberbox', options:{precision:1},
	 * op:['equal','notequal','less','greater'] }]);
	 * 
	 * @param fieldTypeName
	 * @return
	 */
	public static Map<String, Object> defaultColumnFilter(String filedName, String fieldTypeName, String gridId) {
		Map<String, Object> ret = new LinkedHashMap<>();
		ret.put("field", "'" + filedName + "'");

		fieldTypeName = fieldTypeName.toLowerCase();
		String editorType = "textbox";

		Map<String, Object> options = new LinkedHashMap<>();
		switch (fieldTypeName) {
		case "byte":
		case "short":
		case "int":
		case "long":
		case "biginteger":
			options.put("precision", "0");
			editorType = "numberbox";
			break;
		case "float":
		case "double":
		case "decimal":
		case "bigdecimal":
			options.put("precision", "2");
			editorType = "numberbox";
			break;
		case "date":
		case "calendar":
			editorType = "datebox";
			break;
		case "boolean":
			// use Y/N combox
			editorType = "combobox";
			options.put("panelHeight", "'auto'");
			options.put("valueField", "'value'");
			options.put("textField", "'text'");
			options.put("data", "[{value:'Y',text:'是'},{value:'N',text:'否'}]");
			break;
		}

		ret.put("type", "'" + editorType + "'");

		// onchange
		String onchange = FreeMarkerUtil.processTemplate("datagrid-filter-onchange.fmt", "filedName", filedName,
				"gridId", gridId);
		options.put("onChange", onchange);
		ret.put("options", options);

		if (editorType.contains("text")) {
			ret.put("op", "['contains','beginwith','endwith']");
		} else if ("boolean".equals(fieldTypeName)) {
			ret.put("op", "'equals'");
		} else {
			ret.put("op", "['equal','notequal','less','lessorequal','greater','greaterorequal']");
		}

		return ret;
	}

	public static Object getDefaultWidth(String fieldTypeName) {
		switch (fieldTypeName) {
		case "byte":
		case "short":
		case "int":
		case "long":
		case "biginteger":
			return 100;
		case "float":
		case "double":
		case "decimal":
		case "bigdecimal":
			return 150;
		case "date":
		case "calendar":
			return 150;
		case "boolean":
			return 50;
		}
		return 100;
	}
}
